﻿#region

using System;
using System.Diagnostics;
using System.Threading;

#endregion

namespace AjaxControlToolkit.LongOperations
{
    internal class ThreadWrapper
    {
        internal readonly object _syncRoot = new object();
        private readonly bool isPermanent;
        private readonly string name;
        private readonly ThreadPool pool;
        private readonly ThreadPriority priority;
        private Guid _currentWorkRequestGuid = Guid.Empty;
        internal Thread thread;


        public ThreadWrapper(ThreadPool pool, bool isPermanent,
                             ThreadPriority priority, string name)
        {
            this.pool = pool;
            this.isPermanent = isPermanent;
            this.priority = priority;
            this.name = name;

            lock (pool)
            {
                // Update the total # of threads in the pool.
                //
                pool.currentThreadCount++;
                pool.threads.Add(this);
            }
        }

        public Guid CurrentWorkRequestGuid
        {
            get
            {
                lock (_syncRoot)
                {
                    return _currentWorkRequestGuid;
                }
            }
        }

        public void Start()
        {
            lock (_syncRoot)
            {
                thread = new Thread(ThreadProc);
                thread.TrySetApartmentState(ApartmentState.MTA);
                thread.Name = name;
                thread.Priority = priority;
                thread.IsBackground = pool.useBackgroundThreads;
                thread.Start();
            }
        }

        public void Abort()
        {
            lock (_syncRoot)
            {
                if (thread != null)
                {
                    thread.Abort();
                    thread.Join();
                    thread = null;
                    lock (pool)
                    {
                        pool.currentThreadCount--;
                        pool.threads.Remove(this);
                        _currentWorkRequestGuid = Guid.Empty;
                    }
                }
            }
        }

        private void ThreadProc()
        {
            Debug.WriteLine(string.Format("[{0}, {1}] Worker thread started",
                                          AppDomain.GetCurrentThreadId(), Thread.CurrentThread.Name));

            bool done = false;

            while (!done)
            {
                WorkRequest wr = null;
                ThreadWrapper newThread = null;

                lock (pool)
                {
                    // As long as the request queue is empty and a shutdown hasn't
                    // been initiated, wait for a new work request to arrive.
                    //
                    bool timedOut = false;

                    while (!pool.stopInProgress && !timedOut && (pool.requestQueue.Count == 0))
                    {
                        if (!Monitor.Wait(pool, (isPermanent ? Timeout.Infinite : pool.decayTime)))
                        {
                            // Timed out waiting for something to do.  Only dynamically created
                            // threads will get here, so bail out.
                            //
                            timedOut = true;
                        }
                    }

                    // We exited the loop above because one of the following conditions
                    // was met:
                    //   - ThreadPool.Stop was called to initiate a shutdown.
                    //   - A dynamic thread timed out waiting for a work request to arrive.
                    //   - There are items in the work queue to process.

                    // If we exited the loop because there's work to be done,
                    // a shutdown hasn't been initiated, and we aren't a dynamic thread
                    // that timed out, pull the request off the queue and prepare to
                    // process it.
                    //
                    if (!pool.stopInProgress && !timedOut && (pool.requestQueue.Count > 0))
                    {
                        wr = (WorkRequest) pool.requestQueue.Dequeue();
                        Debug.Assert(wr != null);
                        _currentWorkRequestGuid = wr.WorkRequestID;

                        // Check to see if this work request languished in the queue
                        // very long.  If it was in the queue >= the new thread trigger
                        // time, and if we haven't reached the max thread count cap,
                        // add a new thread to the pool.
                        //
                        // If the decision is made, create the new thread object (updating
                        // the current # of threads in the pool), but defer starting the new
                        // thread until the lock is released.
                        //
                        TimeSpan requestTimeInQ = DateTime.Now.Subtract(wr.workingTime);

                        if ((requestTimeInQ >= pool.newThreadTrigger) && (pool.currentThreadCount < pool.maxThreadCount))
                        {
                            // Note - the constructor for ThreadWrapper will update
                            // pool.currentThreadCount.
                            //
                            newThread =
                                new ThreadWrapper(pool, false, priority,
                                                  string.Format("{0} (dynamic)", pool.threadPoolName));

                            // Since the current request we just dequeued is stale,
                            // everything else behind it in the queue is also stale.
                            // So reset the timestamps of the remaining pending work
                            // requests so that we don't start creating threads
                            // for every subsequent request.
                            //
                            pool.ResetWorkRequestTimes();
                        }
                    }
                    else
                    {
                        // Should only get here if this is a dynamic thread that
                        // timed out waiting for a work request, or if the pool
                        // is shutting down.
                        //
                        Debug.Assert((timedOut && !isPermanent) || pool.stopInProgress);
                        pool.currentThreadCount--;
                        pool.threads.Remove(this);

                        if (pool.currentThreadCount == 0)
                        {
                            // Last one out turns off the lights.
                            //
                            Debug.Assert(pool.stopInProgress);
                            pool.Stop();
                            pool.FireStop();
                            pool.stopCompleteEvent.Set();
                        }

                        done = true;
                    }
                } // lock

                // No longer holding pool lock here...

                if (!done && (wr != null))
                {
                    // Check to see if this request has been cancelled while
                    // stuck in the work queue.
                    //
                    // If the work request was pending, mark it processed and proceed
                    // to handle.  Otherwise, the request must have been cancelled
                    // before we plucked it off the request queue.
                    //
                    if (Interlocked.CompareExchange(ref wr.state, WorkRequest.PROCESSED, WorkRequest.PENDING) !=
                        WorkRequest.PENDING)
                    {
                        // Request was cancelled before we could get here.
                        // Bail out.
                        continue;
                    }

                    if (newThread != null)
                    {
                        Debug.WriteLine(string.Format("[{0}, {1}] Adding dynamic thread to pool",
                                                      AppDomain.GetCurrentThreadId(), Thread.CurrentThread.Name));
                        newThread.Start();
                    }

                    // Dispatch the work request.
                    //
                    ThreadInfo originalThreadInfo = null;
                    try
                    {
                        // Impersonate (as much as possible) what we know about
                        // the thread that issued the work request.
                        //
                        originalThreadInfo = ThreadInfo.Impersonate(wr.threadInfo);

                        var targetProc = wr.targetProc as WorkRequestDelegate;

                        if (targetProc != null)
                        {
                            targetProc(wr.procArg, wr.timeStampStarted);
                        }
                        else
                        {
                            wr.targetProc.DynamicInvoke(wr.procArgs);
                        }
                    }
                    catch (ThreadAbortException e)
                    {
                    }
                    catch (Exception e)
                    {
                        if (e.InnerException != null)
                        {
                            InternalLogger.ExceptionHandler(e.InnerException);
                        }
                        else
                        {
                            InternalLogger.ExceptionHandler(e);
                        }
                        Debug.WriteLine(string.Format("Exception thrown performing callback:\n{0}\n{1}", e.Message,
                                                      e.StackTrace));
                    }
                    finally
                    {
                        // Restore our worker thread's identity.
                        //
                        ThreadInfo.Restore(originalThreadInfo);
                        lock (_syncRoot)
                        {
                            _currentWorkRequestGuid = Guid.Empty;
                        }
                    }
                }
            }

            Debug.WriteLine(string.Format("[{0}, {1}] Worker thread exiting pool",
                                          AppDomain.GetCurrentThreadId(), Thread.CurrentThread.Name));
            thread = null;
        }
    }
}